/*

Copyright 2011 James Frank

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

(function($){
    $.fn.extend({ 
        jath: function(options) {
        	context = this;
 
            //Set the default values and get any additional options
            var defaults = {
                ignore: []
            }
            var options =  $.extend(defaults, options);

			
			// check for local storage capabilities
			function storage_check() {
				try {
//					return false; // uncomment to check without localStorage
					return 'localStorage' in window && window['localStorage'] !== null;
				} catch (e) {
					return false;
				}
			}
			supports_storage = storage_check();
			// if we're in a modern browser, let us store ignore flags
			if (supports_storage) {
				// improve localStorage to handle objects
				Storage.prototype.setObject = function(key, value) {
				    this.setItem(key, JSON.stringify(value));
				}
				Storage.prototype.getObject = function(key) {
				    return JSON.parse(this.getItem(key));
				}
//				delete localStorage.accessibilityChecker; // uncomment to reset the localStorage
				if (!localStorage.hasOwnProperty('accessibilityChecker')) {
					localStorage.setObject('accessibilityChecker',new Object);
				}
				aStorage = localStorage.getObject('accessibilityChecker');
				if (!aStorage.hasOwnProperty(document.location.pathname)) {
					aStorage[document.location.pathname] = new Object;
					aStorage[document.location.pathname].path = document.location.pathname;
					aStorage[document.location.pathname].elements = new Array;
				}
				pagePrefs = aStorage[document.location.pathname];
			}
			
			// add the JATH HTML elements to the page
			// create the holder
			$('body').append('\
				<div id="jath-wrapper">\
					<div id="jath-holder">\
						<button id="jath-clear">Clear Page</button>\
						<button id="jath-recheck">Recheck Page</button>\
						<h1>JATH Report</h1>\
						<ul id="jath-error-list"></ul>\
						<button id="jath-full-report">Full JATH Report</button>\
					</div>\
				</div>\
			');
			if (supports_storage) {;
				$('#jath-recheck').after('\
					<button id="jath-reset">Reset Ignored Elements</button>\
					<button id="jath-reset-site">Reset Site</button>\
				');	
			}
			// the trigger for our report
			$('#jath-wrapper').hover(function(){
				$('#jath-holder').slideToggle(50);	
			});
			$('#jath-clear').click(function(){
				clearJath();
				$('#jath-error-list').append('<li>You have cleared the page.</li>');
			});
			// recheck the page
			$('#jath-recheck').click(function(){
				clearJath();
				runJath(context);
			});
			// remove ignores for this page
			$('#jath-reset').click(function(){
				pagePrefs.elements = [];
				localStorage.setObject('accessibilityChecker',aStorage);
				clearJath();
				runJath(context);
			});
			// remove all ignores for entire site
			$('#jath-reset-site').click(function(){
				if(confirm('This will clear all ignored elements on the entire site. Are you sure you want to continue?')) {
					aStorage = new Object;
					localStorage.setObject('accessibilityChecker',aStorage);
					clearJath();
					runJath(context);
				}
			});
			// create and open full jath report
			$('#jath-full-report').click(function() {
				clearJath();
				errors = runJath(context);
				jathWindow = window.open('','jath-full-report-window','toolbar=no,statusbar=no,location=no,scrollbars=yes,resizable=yes,width=500,height=500');
				var jathErrorReport = "";
				var date = new Date;
				jathErrorReport += "<dl><dt>URL</dt><dd><a href='" + document.location + "'>" + document.location + "</a></dd><dt>Date</dt><dd>"+date+"</dd></dl>"
				if (pagePrefs.elements.length > 0) jathErrorReport += "<h2>Ignored Elements</h2><p>There are ignored elements on this page. Reset the ignored elements on the page and re-run JATH for a complete report.</p>"
				for (var error in errors) {
					jathErrorReport += '<h2>'+errors[error].message+'</h2>';
					$.each(errors[error].instances,function(i) {
						id = ($(this).attr('id').length > 0) ? $(this).attr('id') : "<em>null</em>";
						html = $(this).clone().wrap("<div></div>").parent().html();
						jathErrorReport += "<h3>Instance "+(i+1)+" - ID: "+id+"</h3><pre style='background-color: #eee; border: 1px dashed black; padding: 5px; word-wrap: break-word;'>"+html.replace(new RegExp('>', 'g'),'&gt;').replace(new RegExp('<', 'g'),'&lt;')+"</pre>";
					});
				}
				jathFullReport = "\
					<html>\
						<head>\
							<title>JATH Full Report</title>\
						</head>\
						<body>\
							<h1>JATH Full Report</h1>\
							"+jathErrorReport+"\
						</body>\
				";
				jathWindow.document.write(jathFullReport);
			});
			
				
			
			// clear everything that jath does
			function clearJath() {
				$('#jath-error-list').empty();
				$('.jath-parent *:not(.jath-tooltip)').removeData('jathErrors');
				$('.jath-parent').children().unwrap();
			}	

			function runJath(context) {
				
				// set the error object
				var errors = new Array;
			
				// run a test on the elements and add to our error object
				function createTest(hover,message,elements,ignore) {
					if (elements.length > 0 && $.inArray(ignore,options.ignore) == -1) {
						elements = elements.filter(function(){
							// make sure we're not supposed to be ignoring this one
							if (supports_storage && $.inArray($(this).attr('id'),pagePrefs.elements) != -1) return false;
							return true;
						});
						elements.each(function(i,val){
							if ($(this).data('jathErrors') == undefined) {
								$(this).data('jathErrors',[hover])
							} else {
								var elementErrors = $(this).data('jathErrors');
								elementErrors.push(hover);
								$(this).data('jathErrors',elementErrors);	
							}
						});
						error = new Object
						error.instances = elements;
						error.message = message;
						error.hover = hover;
						errors.push(error);
					}
				}	
				
	 			// iterate over the selected elements
	            context.each(function() {
	                
	                // check inputs missingLabels
						createTest(	'This input has no associated label and no title attribute.',
									'There are inputs without an associated label or a title attribute.',
									$(this).find('input:not([type="submit"],[type="hidden"],[type="button"],[type="image"]),textarea,select').filter(function(){
										if ($(this).attr('id').length == 0 || $('label[for="'+$(this).attr('id').replace(new RegExp(':', 'g'),'\\:')+'"]').length == 0 && $(this).attr('title').length == 0) {
											return true;
										} else { return false; }
									}),
									'labels'
						);
							
						// check radios inFieldsets
						createTest(	'This input is not in a fieldset.',
									'There are radio buttons or checkboxes not in fieldsets.',
									$(this).find('input:[type="radio"]').filter(function(){
										if($(this).closest('fieldset').length == 0) return true;
									}),
									'fieldsets'
						);
								
						// check images missingAlts
						createTest(	'This image has no alt text.',
									'There are images without alternate text.',
									$(this).find('img:not([alt]),input[type=image]:not([alt])'),
									'alts'
						);
						
						// check tables for captions or summaries
						createTest(	'This table does not have a caption or summary.',
									'There are tables without captions or summaries.',
									$(this).find('table:not([summary])').filter(function(){
										if ($(this).children('caption').length == 0) return true;
									}),
									'captions'
						);
						
						// check table rows for table headers
						createTest(	'This table does not have appropriate table headers.',
									'There are tables without appropriate table headers.',
									$(this).find('table').filter(function(){
										// if there are no headers flag it
										if ($(this).filter('th').length === 0) return true;
										// if the whole first row is headers don't flag it
										if ($(this).find('tr:eq(0)').children('th').length > 0 && $(this).find('tr:eq(0)').children('td').length == 0) return false;
										// if each row starts with a header, don't flag it
										if ($(this).find('tr').filter(function(){
											if ($(this).children(':eq(0)').filter('th').length == 0) return true;
										}).length == 0) return false;
										// if the first row and column are headers except the top left cell, don't flag it
										if ($(this).find('tr:eq(0)').children(':eq(0)').filter('td').length > 0) {
											if ($(this).find('tr:gt(0)').filter(function(){
												if ($(this).children(':eq(0)').filter('th').length == 0) return true;
											}).length == 0) return false;
										}
										// else flag it
										return true;
									}),
									'headers'
						);
						
						// check legends on fieldsets
						createTest(	'This fieldset does not have a legend.',
									'There are fieldsets without legends.',
									$(this).find('fieldset').filter(function(){
										if ($(this).children('legend').length == 0) return true;
									}),
									'legends'
						);
						
						// check for spans with color and no bold or italics
						createTest(	'This element uses color to identify content without also using bold or italics.',
									'Color is used to identify content without also using bold or italitcs.',
									$(this).find('span').filter(function(){
										if ( 	$(this).attr('style') != undefined &&
												$(this).css('color') != $(this).parent().css('color') &&
												(	$(this).css('font-weight') == 'normal' && 
													$(this).css('font-style') == 'normal'
												)
											) 	{
													if ($(this).children('b,strong,i,em').length == 0) {
														return true;
													} else {
														return false;
													}
												} else {
													return false;
												}
									}),
									'color'
						);
						
					}); 
					
					// do we have errors?
					// process and display them
					$.each(errors, function(i,val) {
						$.each(val.instances, function(i,val) {
							// identify the element
							if (!$(this).parent().hasClass('jath-parent')) $(this).wrap('<span class="jath-parent">');
							if ($(this).filter('[type="radio"],[type="checkbox"]').length > 0) $(this).parent().addClass('jath-parent-color');
							// on hover display a message
							$(this).parent().hover(function(e) {
								$('.jath-tooltip').remove();
								if ($(val).data('jathErrors').length > 1) {
									hover = "<ul>"
									for (error in $(val).data('jathErrors')) {
										hover = hover + "<li>" + $(val).data('jathErrors')[error] + "</li>";
									}
									hover = hover + "</ul>";
								} else {
									hover = $(val).data('jathErrors')[0];
								}
								$(this).append('<span class="jath-tooltip">'+hover+'</span>');
								// be able to set a flag to ignore an element for future checks
								if (supports_storage) {
									$(this).children('.jath-tooltip').append(' <button class="jath-ignore" '+(($(val).attr('id').length == 0) ? 'disabled="disabled"' : '') +'>Ignore</button>');
									$(this).find('.jath-ignore').click(function() {
										if (!($(val).attr('id') in pagePrefs.elements)) pagePrefs.elements.push($(val).attr('id'));
										localStorage.setObject('accessibilityChecker',aStorage);
										$(this).parent().remove(); // get rid of parent tooltip
										$(val).unwrap(); // unwrap the element in question
									});
								}
								// position the tooltip
								tooltip = $(this).find('.jath-tooltip');
								var top = (e.pageY-jQuery(window).scrollTop() < tooltip.outerHeight() ? e.pageY : e.pageY-tooltip.outerHeight());
								var left = (jQuery(window).width() - e.pageX-jQuery(window).scrollLeft() < tooltip.outerWidth() ? e.pageX-tooltip.outerWidth() : e.pageX);
								tooltip.css({
									top: top,
									left: left
								});	
							},
							function() {
								$('.jath-tooltip').remove();
							});	
						});
						// TBD // other stuff
						$('#jath-holder ul').append('<li>'+val.message+'</li>');
					});
					
					if (errors.length > 0) {
						$('#jath-wrapper').css('background-color','red');
					} else {
						$('#jath-wrapper').css('background-color','green');
						$('#jath-error-list').append('<li>Congratulations, no errors!</li>');
					}
					
					return errors;

				}
				runJath(context);
						
			return true;
        }
    });
   
})(jQuery);